我們來建立幾個重複的整數:10 和 257,比對一下他們的ID看看:
var_1 = 10
var_2 = 10
id(var_1), id(var_2)
(4338205200, 4338205200)
var_3 = 257
var_4 = 257
id(var_3), id(var_4)
(4575421584, 4575427280)
怪事發生了!為什麼同樣指向 10 的 var_1 與 var_2 兩個 id 相同,var_3 與 var_4 卻是完全兩個不同的 id 呢?
Python(CPython)啟動時,為了效率更好,會把 -5~256 整數加入到一個 global list 中(類似快取),所以每次我們參考這範圍的整數,就會參考到這些預先產生的物件。
這些整數稱為 singletons,Python認為這些小整數被使用到的機率較大,於是預先準備好。
延續昨天的資源回收話題,當時我們拿了list[1,2,3]來測試,卻沒有用簡單的整數 10 來測試,為什麼呢?即是因為 10 這個整數在Python啟動時已經被預先產生並被參考,因此印出的參考值會與你想像的不同。
整數 257 超過了上述範圍,因此每次我們指派一個整數變數"257",Python都會產生一個新的整數 257 物件。
說完整數來說字串————部分的字串也有interning,比如以下:
Python 用什麼規則抓出以上名稱?字串必須以底線或字母開頭,且只能包含底線、字母和數字。
像 hello_world 這種符合以上條件的 string literals 也會被 interning。
為什麼有 string interning?都是為了速度和記憶體的最佳化。寫程式很常用到字典,在字典裡找東西就要用到 string 的比對。假設我們要比對以下兩個字串是否相同:
a = "this_is_hello_world"
b = "this_is_hello_world"
要比對 a,b 兩個字串是否相同(a == b),通常只能一個字母一個字母比較。但如果我們知道這個字串已經 interned,只要比對 a,b 是否指向同一個記憶體位置就好了!
比較記憶體位置的語法是 a is b
不是所有字串都會被自動 interned,但我們可以手動做這件事。
import time, sys
# 字串夠長才看得出比對時間差異
a = 'I love to go out at Saturday night.' * 100000
b = 'I love to go out at Saturday night.' * 100000
start = time.perf_counter()
for i in range(10000): # 多次比對才看得出時間差異
a == b
end = time.perf_counter()
print("compare with characters:", end - start)
# 字串夠長才看得出比對時間差異
a = sys.intern('I love to go out at Saturday night.' * 100000)
b = sys.intern('I love to go out at Saturday night.' * 100000)
start = time.perf_counter()
for i in range(10000): # 多次比對才看得出時間差異
a is b
end = time.perf_counter()
print("compare with ids:", end - start)
compare with characters: 0.8398300419939915
compare with ids: 0.000305499997921288
可以看到比對的時間差距很大!這在比對大量字串時可能會很好用。不過一般來說,你不需要自己去做 string interning。
我們明天見~
參考:Python 3: Deep Dive (Part 1 - Functional)